package com.myssm.myspringmvc;

import com.myssm.ioc.BeanFactory;
import com.myssm.ioc.ClassPathXmlApplicationContext;
import com.myssm.util.StringUtil;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @author Jack Tang
 * @description
 * @create 2023-05-07 19:22
 */
@WebServlet("*.do") // 拦截所有以 .do 结尾的请求
public class DispatcherServlet extends ViewBaseServlet {

    private BeanFactory beanFactory;

    public DispatcherServlet(){


    }

    public void init() throws ServletException {
        super.init();
        // 之前是在此主动创建IOC容器的
        // 现在优化为从application作用域去获取
        //beanFactory = new ClassPathXmlApplicationContext();
        ServletContext application = getServletContext();
        Object beanFactoryObj =  application.getAttribute("beanFactory");
        if (beanFactoryObj != null) {
            beanFactory = (BeanFactory)beanFactoryObj;
        }else {
            throw new RuntimeException("IOC容器获取失败！");
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置编码
        req.setCharacterEncoding("utf-8");

        // 假设url是： http://localhost:8080/day8/fruit.do
        //那么 servletPath 是: /hello.do
        // 具体思路是：
        // 第一步： 通过 getServletPath() 这个函数获取到了 /hello.do
        // 第二步： 通过字符串截取得到 hello
        // 第三步： 让hello 和 HelloController 能对应上
        String servletPath = req.getServletPath();  //通过这个函数获取到了 /hello.do
        //通过字符串截取得到 hello
        int lastDotIndex = servletPath.lastIndexOf(".do");
        servletPath = servletPath.substring(1, lastDotIndex);

        Object controllerBeanObj = beanFactory.getBean(servletPath);


        String operate = req.getParameter("operate");
        if (StringUtil.isEmpty(operate)){
            operate = "index";
        }

        try {
            Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
            for (Method method : methods) {
                if (operate.equals(method.getName())){
                    // 1. 统一获取请求参数

                    // 1.1 获取当前方法的参数，返回参数数组
                    Parameter[] parameters = method.getParameters();
                    // 1.2 parameterValues 用来存放参数的值
                    Object[] parameterValues = new Object[parameters.length];
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        String parameterName = parameter.getName();
                        // 如果参数名是 req,resp,session 那么就不是通过请求中获取参数的方式了
                        if ("req".equals(parameterName)) {
                            parameterValues[i] = req;
                        }else if ("resp".equals(parameterName)){
                            parameterValues[i] = resp;
                        }else if ("session".equals(parameterName)){
                            parameterValues[i] = req.getSession();
                        }else {
                            // 从请求中获取参数值
                            String parameterValue = req.getParameter(parameterName);
                            // 通过调用parameter的getType()方法可以获取到该参数的类型信息，
                            // 进而再使用getName()方法获取该类型的名称，最终存储在typeName变量中
                            String typeName = parameter.getType().getName();

                            Object parameterObj = parameterValue;

                            if (parameterObj != null) {
                                if ("java.lang.Integer".equals(typeName)) {
                                    parameterObj = Integer.parseInt(parameterValue);
                                }
                            }
                            parameterValues[i] = parameterObj;
                        }
                    }

                    // 2.Controller 组件中的方法调用
                    //将 Method 对象的 accessible 标志设置为 true，从而可以访问该方法，即使该方法是私有的或受保护的。
                    method.setAccessible(true);
                    // m.invoke() 方法会将 this 对象作为方法的调用者，也就是说，该方法会在当前对象上执行。
                    // controllerBeanObj 就是要调用对象的方法
                    Object returnObj = method.invoke(controllerBeanObj, parameterValues);

                    String methodReturnStr = null;
                    // 3.视图处理
                    if (returnObj != null) {
                        methodReturnStr = (String) returnObj;
                    }
                    if (methodReturnStr.startsWith("redirect:")){  // 比如 "redirect:fruit.do"
                        String redirectStr = methodReturnStr.substring("redirect:".length());
                        resp.sendRedirect(redirectStr);
                    }else {
                        super.processTemplate(methodReturnStr,req,resp); // 比如 "edit"
                    }
                }
            }

        }  catch (Exception e) {
            e.printStackTrace();
            throw new DispatcherServletException("Dispatcher service出错了");
        }


    }
}


// 常见错误： IllegalArgumentException: argument type mismatch